import { $set } from '@feng3d/serialization';
import { CanvasTexture3D, Clock, Color, FogExp23D, Mesh3D, MeshBasicMaterial3D, Node3D, PerspectiveCamera3D, PlaneGeometry3D, Scene3D, StatsComponent, Vector3, WebGLRenderer3D } from 'feng3d-three';
import { FirstPersonControls3D } from '../../extension/controls/FirstPersonControls3D';
import { ImprovedNoise } from '../../extension/thirdparty/three/imports';

let container: HTMLElement;
let stats: StatsComponent;
let camera: PerspectiveCamera3D;
let controls: FirstPersonControls3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let mesh: Mesh3D;
let texture: CanvasTexture3D;

const worldWidth = 256;
const worldDepth = 256;
const clock = new Clock();

init();
animate();

function init()
{
    container = document.getElementById('container');

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 60, aspect: window.innerWidth / window.innerHeight, near: 1, far: 10000 });

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0xefd1b5);
    scene.fog = new FogExp23D(0xefd1b5, 0.0025);

    const data = generateHeight(worldWidth, worldDepth);

    camera.entity.position.set(100, 800, -800);
    camera.entity.lookAt(-100, 810, -800);

    const geometry = $set(new PlaneGeometry3D(), { width: 7500, height: 7500, widthSegments: worldWidth - 1, heightSegments: worldDepth - 1 });
    geometry.rotateX(-Math.PI / 2);

    const vertices = geometry.attributes.position.array;

    for (let i = 0, j = 0, l = vertices.length; i < l; i++, j += 3)
    {
        vertices[j + 1] = data[i] * 10;
    }

    texture = new CanvasTexture3D(generateTexture(data, worldWidth, worldDepth));
    texture.wrapS = 'ClampToEdgeWrapping';
    texture.wrapT = 'ClampToEdgeWrapping';

    mesh = new Node3D().addComponent('Mesh3D', {
        geometry, material: $set(new MeshBasicMaterial3D(), { map: texture })
    });
    scene.entity.addChild(mesh.entity);

    renderer = scene.addComponent('WebGLRenderer3D');
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    controls = new FirstPersonControls3D(camera, renderer.domElement);
    controls.movementSpeed = 150;
    controls.lookSpeed = 0.1;

    stats = scene.addComponent('StatsComponent');
    container.appendChild(stats.dom);

    //

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);

    controls.handleResize();
}

function generateHeight(width: number, height: number)
{
    let seed = Math.PI / 4;
    window.Math.random = function ()
    {
        const x = Math.sin(seed++) * 10000;

        return x - Math.floor(x);
    };

    const size = width * height;
    const data = new Uint8Array(size);
    const perlin = new ImprovedNoise();
    const z = Math.random() * 100;

    let quality = 1;

    for (let j = 0; j < 4; j++)
    {
        for (let i = 0; i < size; i++)
        {
            const x = i % width; const
                y = ~~(i / width);
            data[i] += Math.abs(perlin.noise(x / quality, y / quality, z) * quality * 1.75);
        }

        quality *= 5;
    }

    return data;
}

function generateTexture(data: Uint8Array, width: number, height: number)
{
    let context: CanvasRenderingContext2D;
    let image: ImageData;
    let imageData: Uint8ClampedArray;
    let shade: number;

    const vector3 = new Vector3(0, 0, 0);

    const sun = new Vector3(1, 1, 1);
    sun.normalize();

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    context = canvas.getContext('2d');
    context.fillStyle = '#000';
    context.fillRect(0, 0, width, height);

    image = context.getImageData(0, 0, canvas.width, canvas.height);
    imageData = image.data;

    for (let i = 0, j = 0, l = imageData.length; i < l; i += 4, j++)
    {
        vector3.x = data[j - 2] - data[j + 2];
        vector3.y = 2;
        vector3.z = data[j - width * 2] - data[j + width * 2];
        vector3.normalize();

        shade = vector3.dot(sun);

        imageData[i] = (96 + shade * 128) * (0.5 + data[j] * 0.007);
        imageData[i + 1] = (32 + shade * 96) * (0.5 + data[j] * 0.007);
        imageData[i + 2] = (shade * 96) * (0.5 + data[j] * 0.007);
    }

    context.putImageData(image, 0, 0);

    // Scaled 4x

    const canvasScaled = document.createElement('canvas');
    canvasScaled.width = width * 4;
    canvasScaled.height = height * 4;

    context = canvasScaled.getContext('2d');
    context.scale(4, 4);
    context.drawImage(canvas, 0, 0);

    image = context.getImageData(0, 0, canvasScaled.width, canvasScaled.height);
    imageData = image.data;

    for (let i = 0, l = imageData.length; i < l; i += 4)
    {
        const v = ~~(Math.random() * 5);

        imageData[i] += v;
        imageData[i + 1] += v;
        imageData[i + 2] += v;
    }

    context.putImageData(image, 0, 0);

    return canvasScaled;
}

//

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    controls.update(clock.getDelta());
    renderer.render(scene, camera);
}
